/*
 * Copyright 2023, Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Haiku Development Team
 */

#include <arch/generic/debug_uart_8250.h>
#include <arch/generic/debug_uart.h>

#include <boot/stage2.h>
#include <kernel.h>

// 3A6000 UART registers (based on 16550 UART)
#define UART_3A6000_REG_RBR 0x00 // Receiver Buffer Register (read)
#define UART_3A6000_REG_THR 0x00 // Transmitter Holding Register (write)
#define UART_3A6000_REG_IER 0x01 // Interrupt Enable Register
#define UART_3A6000_REG_IIR 0x02 // Interrupt Identification Register (read)
#define UART_3A6000_REG_FCR 0x02 // FIFO Control Register (write)
#define UART_3A6000_REG_LCR 0x03 // Line Control Register
#define UART_3A6000_REG_MCR 0x04 // Modem Control Register
#define UART_3A6000_REG_LSR 0x05 // Line Status Register
#define UART_3A6000_REG_MSR 0x06 // Modem Status Register
#define UART_3A6000_REG_SCR 0x07 // Scratch Register

// Line Status Register bits
#define UART_3A6000_LSR_DR 0x01   // Data Ready
#define UART_3A6000_LSR_THRE 0x20 // Transmitter Holding Register Empty

// Line Control Register bits
#define UART_3A6000_LCR_DLAB 0x80 // Divisor Latch Access Bit
#define UART_3A6000_LCR_8N1 0x03  // 8 bits, no parity, 1 stop bit

// FIFO Control Register bits
#define UART_3A6000_FCR_ENABLE_FIFO 0x01 // Enable FIFO
#define UART_3A6000_FCR_CLEAR_RCVR 0x02  // Clear Receiver FIFO
#define UART_3A6000_FCR_CLEAR_XMIT 0x04  // Clear Transmitter FIFO

class DebugUART3A6000 : public DebugUART8250
{
public:
    DebugUART3A6000(addr_t base, int64 clock)
        : DebugUART8250(base, clock)
    {
    }

    virtual ~DebugUART3A6000()
    {
    }

    virtual void InitEarly()
    {
        // Wait for transmitter to be empty
        while ((In8(UART_3A6000_REG_LSR) & UART_3A6000_LSR_THRE) == 0)
            ;

        // Disable all interrupts
        Out8(UART_3A6000_REG_IER, 0x00);

        // Enable FIFO, clear FIFOs, set interrupt trigger level
        Out8(UART_3A6000_REG_FCR, UART_3A6000_FCR_ENABLE_FIFO |
                                      UART_3A6000_FCR_CLEAR_RCVR | UART_3A6000_FCR_CLEAR_XMIT);

        // Set 8N1 mode (8 bits, no parity, 1 stop bit)
        Out8(UART_3A6000_REG_LCR, UART_3A6000_LCR_8N1);

        // Set baud rate divisor if clock is provided
        if (fClock > 0)
        {
            // Calculate divisor for baud rate 115200
            uint16 divisor = fClock / (16 * 115200);

            // Set DLAB to access divisor latches
            Out8(UART_3A6000_REG_LCR, UART_3A6000_LCR_DLAB | UART_3A6000_LCR_8N1);

            // Set divisor (low and high bytes)
            Out8(UART_3A6000_REG_RBR, divisor & 0xff);
            Out8(UART_3A6000_REG_IER, (divisor >> 8) & 0xff);

            // Reset DLAB
            Out8(UART_3A6000_REG_LCR, UART_3A6000_LCR_8N1);
        }
    }

    virtual void InitPort(uint32 baudRate)
    {
        // Similar to InitEarly but with configurable baud rate
        if (fClock > 0 && baudRate > 0)
        {
            uint16 divisor = fClock / (16 * baudRate);

            // Set DLAB to access divisor latches
            Out8(UART_3A6000_REG_LCR, UART_3A6000_LCR_DLAB | UART_3A6000_LCR_8N1);

            // Set divisor (low and high bytes)
            Out8(UART_3A6000_REG_RBR, divisor & 0xff);
            Out8(UART_3A6000_REG_IER, (divisor >> 8) & 0xff);

            // Reset DLAB
            Out8(UART_3A6000_REG_LCR, UART_3A6000_LCR_8N1);
        }
    }

    virtual void PutChar(char c)
    {
        // Wait for transmitter to be empty
        while ((In8(UART_3A6000_REG_LSR) & UART_3A6000_LSR_THRE) == 0)
            ;

        // Send character
        Out8(UART_3A6000_REG_THR, c);
    }

    virtual bool DataReady()
    {
        return (In8(UART_3A6000_REG_LSR) & UART_3A6000_LSR_DR) != 0;
    }

    virtual char GetChar(bool wait)
    {
        if (wait)
        {
            // Wait for data to be available
            while (!DataReady())
                ;
        }
        else if (!DataReady())
        {
            return 0;
        }

        // Read character
        return In8(UART_3A6000_REG_RBR);
    }
};

DebugUART *
create_debug_uart_3a6000(addr_t base, int64 clock)
{
    return new (nothrow) DebugUART3A6000(base, clock);
}